
#define DUA_CORE

#include "dua_order.h"
#include "dua_opcode.h"

typedef struct {
	unsigned pos;
	int bct;
	int type;
	DuaString* name;
	unsigned index;
}Reg;

typedef struct {
	unsigned pos;
}TempReg;

typedef struct {
	DuaObj obj;
	unsigned top;
	uint32_t* code;
	unsigned codeCount;
	unsigned codeCap;
	Reg* regs;
	unsigned regCap;
	unsigned regCount;
	DuaValue* k;
	unsigned kCap;
	unsigned kCount;
	//Reg* names;
	//unsigned nameCount;
	//unsigned nameCap;
	//Reg* temps;
	//unsigned tempCount;
	//unsigned tempCap;
}Compiler;

static inline unsigned
pushK(DuaState* D, Compiler* com, DuaTarget* v) {
	if (com->kCount >= com->kCap) {
		unsigned cap = com->kCap * 2;
		com->k = duaRelloc(D, com->k,
			sizeof(DuaValue) * com->kCap, sizeof(DuaValue) * cap);
		com->kCap = cap;
	}
	com->k[com->kCount++] = v->value;
	return 1;
}

static inline void
pushCode(DuaState* D, Compiler* com,
	int op, int r1, int r2, int flag) {
	if (com->codeCount >= com->codeCap) {
		unsigned cap = com->codeCap * 2;
		com->code = duaRelloc(D, com->code,
			sizeof(uint32_t) * com->codeCap, sizeof(uint32_t) * cap);
		com->codeCap = cap;
	}
	com->code[com->codeCount++] = duaMakeCode(op, r1, r2, flag);
}

static inline void
expandRegs(DuaState* D, Compiler* com) {
	if (com->regCount >= com->regCap) {
		unsigned cap = com->regCap * 2;
		com->regs = duaRelloc(D, com->regs,
			sizeof(Reg) * com->regCap, sizeof(Reg) * cap);
		com->regCap = cap;
	}
}

static inline Reg*
regK(DuaState* D, Compiler* com, unsigned k) {
	Reg* reg = NULL;
	for (unsigned i = 0; i < com->regCount; i++) {
		reg = &com->regs[i];
		if (reg->bct == TARGET_TEMP && reg->index == k) {
			break;
		}
		else {
			reg = NULL;
		}
	}
	return reg;
}

static inline Reg*
regTemp(DuaState* D, Compiler* com, unsigned temp) {
	Reg* reg = NULL;
	for (unsigned i = 0; i < com->regCount; i++) {
		reg = &com->regs[i];
		if (reg->bct == TARGET_TEMP && reg->index == temp) {
			break;
		}
		else {
			reg = NULL;
		}
	}
	return reg;
}

static inline Reg*
regName(DuaState* D, Compiler* com, DuaString* name) {
	Reg* reg = NULL;
	for (unsigned i = 0; i < com->regCount; i++) {
		reg = &com->regs[i];
		if (reg->bct == TARGET_NAME && reg->name == name) {
			break;
		}
		else {
			reg = NULL;
		}
	}
	return reg;
}

static inline Reg*
newReg(DuaState* D, Compiler* com, DuaTarget* target) {
	expandRegs(D, com);
	Reg* reg = &com->regs[com->regCount++];
	reg->bct = target->type;
	reg->pos = 0;
	switch (reg->bct) {
	case TARGET_TEMP:
	case TARGET_CONST:
		reg->index = target->index;
		break;
	case TARGET_NAME:
		reg->name = target->name;
		break;
	}
	return reg;
}

static Reg*
findReg(DuaState* D, Compiler* com, DuaTarget* target) {
	Reg* reg = NULL;
	switch (target->type) {
	case TARGET_CONST:
		reg = regK(D, com, target->index);
		break;
	case TARGET_TEMP:
		reg = regTemp(D, com, target->index);
		break;
	case TARGET_NAME:
		reg = regName(D, com, target->name);
		break;
	}
	return reg;
}

static unsigned
letOrder(DuaState* D, Compiler* com, DuaOrder* order) {
	Reg* res = findReg(D, com, &order->res);
	if (res) {
		duaRet(D, 1, "left value already exist");
	}
	res = newReg(D, com, &order->res);
	res->pos = com->top++;
	Reg* v1 = findReg(D, com, &order->v1);
	switch (order->v1.type) {
	case TARGET_CONST:
		if (!v1) { v1 = newReg(D, com, &order->v1); }
		if (v1->index >= com->kCount) {
			duaRet(D, DUA_ERROR, "const index overload");
		}
		
		res->type = com->k[v1->index].type;
		if (v1->pos) {
			pushCode(D, com, OP_COPY, res->pos, v1->pos, 0);
		}
		else {
			pushCode(D, com, OP_LOAD, v1->index, 0, 0);
		}
		break;
	case TARGET_TEMP:
	case TARGET_NAME:
		if (!v1) {
			duaRet(D, DUA_ERROR, "compile error:right reg not exist");
		}
		res->type = v1->type;
		pushCode(D, com, OP_COPY, res->pos, v1->pos, 0);
		break;
	}
	return 1;
}

static unsigned
valueOrder(DuaState* D, Compiler* com, DuaOrder* order) {
	Reg* res = findReg(D, com, &order->res);
	if (!res) {
		duaRet(D, 1, "left reg not found");
	}
	Reg* v1 = findReg(D, com, &order->v1);
	switch (order->v1.type) {
	case TARGET_CONST:
		if (!v1) { v1 = newReg(D, com, &order->v1); }
		if (v1->index >= com->kCount) {
			duaRet(D, DUA_ERROR, "const index overload");
		}
		if (res->type != com->k[v1->index].type) {
			duaRet(D, DUA_ERROR, "type error");
		}
		if (v1->pos) {
			pushCode(D, com, OP_COPY, res->pos, v1->pos, 0);
		}
		else {
			pushCode(D, com, OP_LOAD, v1->index, 0, 0);
			pushCode(D, com, OP_MOVE, res->pos, 0, 0);
		}
		break;
	case TARGET_TEMP:
	case TARGET_NAME:
		if (!v1) {
			duaRet(D, DUA_ERROR, "compile error:right reg not exist");
		}
		pushCode(D, com, OP_COPY, res->pos, v1->pos, 0);
		break;
	}
	return 1;
}

static void
arithCode(DuaState* D, Compiler* com, DuaOrder* order) {

}

static void
printCodes(DuaState* D, Compiler* com) {

	static const char* OP_NAMES[] = {
		"load", "move", "pop", "copy",
	};
	DuaValue* v;
	for (unsigned i = 0; i < com->kCount; i++) {
		v = &com->k[i];
		printf("= ");
		switch (v->type) {
		case DUA_TINT:
			printf("%d:int\n", v->i);
			break;
		case DUA_TLONG:
			printf("%lld:long\n", v->l);
			break;
		case DUA_TFLOAT:
			printf("%f:float\n", v->f);
			break;
		case DUA_TDOUBLE:
			printf("%lf:double\n", v->d);
			break;
		case DUA_TSTRING:
			printf("%s:string\n", v->ts->s);
			break;
		}
	}

	uint32_t code, cOp, cR1, cR2, cFlag;
	for (unsigned i = 0; i < com->codeCount; i++) {
		code = com->code[i];
		duaReadCode(code, c);
		printf(".%s %d %d %d\n", OP_NAMES[cOp - OP_LOAD], cR1, cR2, cFlag);
	}

}

extern DuaProto*
duaCompile(DuaState* D, DuaOrder* orders, unsigned count) {
	Compiler* com = duaNewObj(D, 0, sizeof(Compiler));
	com->top = 1;
	com->k = NULL;
	com->code = NULL;
	com->kCount = 0;
	com->k = duaMalloc(D, sizeof(DuaValue) * 8);
	com->kCount = 0;
	com->kCap = 8;
	com->code = duaMalloc(D, sizeof(uint32_t) * 16);
	com->codeCap = 16;
	com->codeCount = 0;
	com->regs = duaMalloc(D, sizeof(Reg) * 8);
	com->regCap = 8;
	com->regCount = 0;

	DuaOrder* order;
	unsigned i = 0;
	while (i < count) {
		order = &orders[i];
		switch (order->order) {
		case ORDER_K:
			i += pushK(D, com, &order->res);
			break;
		case ORDER_LET:
			i += letOrder(D, com, order);
			break;
		case ORDER_VALUE:
			i += valueOrder(D, com, order);
			break;
		default:
			i++;
			break;
		}
	}
	printCodes(D, com);
	return NULL;
}